package service

import (
	"context"
	"encoding/json"
	"errors"
	"gitee.com/Frank098/devops-app-mgt/cache"
	"gitee.com/Frank098/devops-app-mgt/model"
	"gitee.com/Frank098/devops-app-mgt/utils"
	appsv1 "k8s.io/api/apps/v1"
	corev1 "k8s.io/api/core/v1"
	"k8s.io/apimachinery/pkg/api/resource"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/runtime/schema"
	"k8s.io/apimachinery/pkg/util/intstr"
	"sigs.k8s.io/yaml"
	"strconv"
	"time"
)

var Deployment deployment

type deployment struct{}

func (d *deployment) GetDeploymentInfo(deploymentName, namespace string) (deployment *appsv1.Deployment, err error) {
	deployment, err = cache.DeploymentCache.GetDeploymentRedisCache("deployment" + "_" + deploymentName + "_" + namespace)
	if err != nil {
		deployment, err = K8s.ClientSet.AppsV1().Deployments(namespace).Get(context.TODO(), deploymentName, metav1.GetOptions{})
		if err != nil {
			return nil, errors.New("获取Deployment详情失败, " + err.Error())
		}
		if err = cache.DeploymentCache.SetDeploymentRedisCache("deployment"+"_"+deploymentName+"_"+namespace, deployment); err != nil {
			return nil, errors.New("写入缓存数据失败," + err.Error())
		}
		return deployment, nil
	}
	return deployment, nil
}

func (d *deployment) ScaleDeployment(deploymentName, namespace string, scaleNum int) (replica int32, err error) {
	//获取autoscalingv1.Scale类型的对象，能点出当前的副本数
	scale, err := K8s.ClientSet.AppsV1().Deployments(namespace).GetScale(context.TODO(), deploymentName, metav1.GetOptions{})
	if err != nil {
		return 0, errors.New("获取Deployment副本数信息失败, " + err.Error())
	}
	//修改副本数
	scale.Spec.Replicas = int32(scaleNum)
	//更新副本数，传入scale对象
	newScale, err := K8s.ClientSet.AppsV1().Deployments(namespace).UpdateScale(context.TODO(), deploymentName, scale, metav1.UpdateOptions{})
	if err != nil {
		return 0, errors.New("更新Deployment副本数信息失败, " + err.Error())
	}
	return newScale.Spec.Replicas, nil
}

func (d *deployment) CreateDeployment(data *model.DeployCreate) (err error) {
	//将data中的属性组装成appsv1.Deployment对象,并将入参数据放入
	deployment := &appsv1.Deployment{
		//ObjectMeta中定义资源名、命名空间以及标签
		ObjectMeta: metav1.ObjectMeta{
			Name:      data.Name,
			Namespace: data.Namespace,
		},
		//Spec中定义副本数、选择器、以及pod属性
		Spec: appsv1.DeploymentSpec{
			Replicas: int32Ptr(data.Replicas),
			Selector: &metav1.LabelSelector{
				MatchLabels: data.Label,
			},
			Template: corev1.PodTemplateSpec{
				//定义pod名和标签
				ObjectMeta: metav1.ObjectMeta{
					Name: data.Name,
				},
				//定义容器名、镜像和端口
				Spec: corev1.PodSpec{
					Containers: []corev1.Container{
						{
							Name:  data.Name,
							Image: data.Image,
						},
					},
				},
			},
		},
		//Status定义资源的运行状态，这里由于是新建，传入空的appsv1.DeploymentStatus{}对象即可
		Status: appsv1.DeploymentStatus{},
	}

	//判断是否打开健康检查功能，若打开，则定义ReadinessProbe和LivenessProbe
	if data.HealthCheck {
		//设置第一个容器的ReadinessProbe，因为我们pod中只有一个容器，所以直接使用index 0即可
		//若pod中有多个容器，则这里需要使用for循环去定义了
		deployment.Spec.Template.Spec.Containers[0].ReadinessProbe = &corev1.Probe{
			ProbeHandler: corev1.ProbeHandler{
				HTTPGet: &corev1.HTTPGetAction{
					Path: data.HealthPath,
					//intstr.IntOrString的作用是端口可以定义为整型，也可以定义为字符串
					//Type=0则表示表示该结构体实例内的数据为整型，转json时只使用IntVal的数据
					//Type=1则表示表示该结构体实例内的数据为字符串，转json时只使用StrVal的数据
					Port: intstr.IntOrString{
						Type:   0,
						IntVal: data.HealthPort,
					},
				},
			},
			InitialDelaySeconds: 60,
			TimeoutSeconds:      1,
			PeriodSeconds:       30,
		}
		deployment.Spec.Template.Spec.Containers[0].LivenessProbe = &corev1.Probe{
			ProbeHandler: corev1.ProbeHandler{
				HTTPGet: &corev1.HTTPGetAction{
					Path: data.HealthPath,
					Port: intstr.IntOrString{
						Type:   0,
						IntVal: data.HealthPort,
					},
				},
			},
			InitialDelaySeconds: 60,
			TimeoutSeconds:      1,
			PeriodSeconds:       30,
		}
		//定义容器的limit和request资源
		deployment.Spec.Template.Spec.Containers[0].Resources.Limits = map[corev1.ResourceName]resource.Quantity{
			corev1.ResourceCPU:    resource.MustParse(data.LimitsCpu),
			corev1.ResourceMemory: resource.MustParse(data.LimitsMemory),
		}
		deployment.Spec.Template.Spec.Containers[0].Resources.Requests = map[corev1.ResourceName]resource.Quantity{
			corev1.ResourceCPU:    resource.MustParse(data.RequestsCpu),
			corev1.ResourceMemory: resource.MustParse(data.RequestsMemory),
		}
		//组装环境变量
		var envVar []corev1.EnvVar
		for _, value := range data.Env {
			for _, valueList := range value {
				ev := corev1.EnvVar{
					Name:  valueList.EnvKey,
					Value: valueList.EnvValue,
				}
				envVar = append(envVar, ev)
			}
			deployment.Spec.Template.Spec.Containers[0].Env = envVar
		}
		//组装容器端口
		var containerPort []corev1.ContainerPort
		for _, value := range data.ContainerPort {
			for _, valueList := range value {
				cp := corev1.ContainerPort{
					Name:          valueList.ContainerPortName,
					Protocol:      corev1.ProtocolTCP,
					ContainerPort: valueList.ContainerPort,
				}
				containerPort = append(containerPort, cp)
			}
			deployment.Spec.Template.Spec.Containers[0].Ports = containerPort
		}
	}
	//组装标签与选择器
	var label = make(map[string]string)
	for key, value := range data.Label {
		label[key] = value
	}
	deployment.ObjectMeta.Labels = label
	deployment.Spec.Template.ObjectMeta.Labels = label

	//调用sdk创建deployment
	_, err = K8s.ClientSet.AppsV1().Deployments(data.Namespace).Create(context.TODO(), deployment, metav1.CreateOptions{})
	if err != nil {
		return errors.New("创建Deployment失败, " + err.Error())
	}
	return nil
}

func int32Ptr(i int32) *int32 { return &i }

func (d *deployment) DeleteDeployment(deploymentName, namespace string) (err error) {
	err = K8s.ClientSet.AppsV1().Deployments(namespace).Delete(context.TODO(), deploymentName, metav1.DeleteOptions{})
	if err != nil {
		return errors.New("删除Deployment失败, " + err.Error())
	}
	return nil
}

func (d *deployment) RestartDeployment(deploymentName, namespace string) (err error) {
	//此功能等同于一下kubectl命令
	//kubectl deployment ${service} -p \
	//'{"spec":{"template":{"spec":{"containers":[{"name":"'"${service}"'","env":[{"name":"RESTART_","value":"'$(date +%s)'"}]}]}}}}'
	//使用patchData Map组装数据
	patchData := map[string]interface{}{
		"spec": map[string]interface{}{
			"template": map[string]interface{}{
				"spec": map[string]interface{}{
					"containers": []map[string]interface{}{
						{"name": deploymentName,
							"env": []map[string]string{{
								"name":  "RESTART_",
								"value": strconv.FormatInt(time.Now().Unix(), 10),
							}},
						},
					},
				},
			},
		},
	}
	//序列化为字节，因为patch方法只接收字节类型参数
	patchByte, err := json.Marshal(patchData)
	if err != nil {
		return errors.New("json序列化失败, " + err.Error())
	}
	//调用patch方法更新deployment
	_, err = K8s.ClientSet.AppsV1().Deployments(namespace).Patch(context.TODO(), deploymentName, "application/strategic-merge-patch+json", patchByte, metav1.PatchOptions{})
	if err != nil {
		return errors.New("重新编排Deployment失败, " + err.Error())
	}
	return nil
}

func (d *deployment) GetDeployNumPerNp() (deploysNps []*model.DeploysNp, err error) {
	deploysNps, err = cache.DeploymentCache.GetDeploymentNpRedisCache("deploysNps")
	if err != nil {
		namespaceList, err := K8s.ClientSet.CoreV1().Namespaces().List(context.TODO(), metav1.ListOptions{})
		if err != nil {
			return nil, err
		}
		for _, namespace := range namespaceList.Items {
			//获取pod列表
			deploysNp, err := d.GetDeploySingleNp(namespace.Name)
			if err != nil {
				return nil, errors.New("获取Deployment列表失败," + err.Error())
			}
			deploysNps = append(deploysNps, deploysNp...)
		}
		if err = cache.DeploymentCache.SetDeploymentNpRedisCache("deploysNps", deploysNps); err != nil {
			return nil, errors.New("写入缓存数据失败," + err.Error())
		}
		return deploysNps, nil
	}
	return deploysNps, nil
}

func (d *deployment) GetDeploySpecial() (deploysNps []*model.DeploysNp, err error) {
	deploysNps, err = cache.DeploymentCache.GetDeploymentNpRedisCache("deploysNpsSpecial")
	if err != nil {
		//指定获取某个命名空间的deployment资源列表
		var namespaceArray = []string{"default", "foo"}
		for _, value := range namespaceArray {
			if value == "" {
				break
			}
			//获取pod列表
			deploysNp, err := d.GetDeploySingleNp(value)
			if err != nil {
				return nil, errors.New("获取Deployment列表失败," + err.Error())
			}
			deploysNps = append(deploysNps, deploysNp...)
		}
		if err = cache.DeploymentCache.SetDeploymentNpRedisCache("deploysNpsSpecial", deploysNps); err != nil {
			return nil, errors.New("写入缓存数据失败," + err.Error())
		}
		return deploysNps, nil
	}
	return deploysNps, nil
}

func (d *deployment) GetDeploySingleNp(namespace string) (deploysNps []*model.DeploysNp, err error) {
	deploysNps, err = cache.DeploymentCache.GetDeploymentNpRedisCache("deploysNps_" + namespace)
	if err != nil {
		deploymentList, err := K8s.ClientSet.AppsV1().Deployments(namespace).List(context.TODO(), metav1.ListOptions{})
		if err != nil {
			return nil, err
		}
		for _, deploymentListInfo := range deploymentList.Items {
			currentContainerTime := deploymentListInfo.CreationTimestamp
			dateOfDifference := utils.SubDays(time.Now(), currentContainerTime.Time)
			deploysNp := &model.DeploysNp{
				Namespace: namespace,
				DeployConventionInfo: &model.DeployConventionInfo{
					Status:            string(deploymentListInfo.Status.Conditions[0].Type),
					Name:              deploymentListInfo.Name,
					Image:             deploymentListInfo.Spec.Template.Spec.Containers[0].Image,
					Replicas:          deploymentListInfo.Status.Replicas,
					UpdatedReplicas:   deploymentListInfo.Status.UpdatedReplicas,
					ReadyReplicas:     deploymentListInfo.Status.ReadyReplicas,
					AvailableReplicas: deploymentListInfo.Status.AvailableReplicas,
					Age:               dateOfDifference,
				},
			}
			deploysNps = append(deploysNps, deploysNp)
		}
		if err = cache.DeploymentCache.SetDeploymentNpRedisCache("deploysNps_"+namespace, deploysNps); err != nil {
			return nil, errors.New("写入缓存数据失败," + err.Error())
		}
		return deploysNps, nil
	}
	return deploysNps, nil
}

func (d *deployment) GetDeployYaml(deploymentName, namespace string) (DeployYaml *model.DeployYamlResponse, err error) {
	DeployYaml, err = cache.DeploymentCache.GetYamlRedisCache("deployment" + "_" + deploymentName + "_" + namespace + "_Yaml")
	if err != nil {
		deployYamlInfo, err := K8s.ClientSet.AppsV1().Deployments(namespace).Get(context.Background(), deploymentName, metav1.GetOptions{})
		if err != nil {
			return nil, errors.New("获取Deployment编排文件失败," + err.Error())
		}
		deployYamlInfo.ManagedFields = []metav1.ManagedFieldsEntry{}
		deployYamlInfo.GetObjectKind().SetGroupVersionKind(schema.GroupVersionKind{
			Group:   "apps",
			Version: "v1",
			Kind:    "Deployment",
		})
		// 将对象解析为yaml，返回[]byte 和err错误信息
		deployYamlByte, err := yaml.Marshal(deployYamlInfo)
		if err != nil {
			return nil, errors.New("对象解析为yaml失败," + err.Error())
		}
		DeployYaml = &model.DeployYamlResponse{
			DeployYaml: string(deployYamlByte),
		}
		if err = cache.DeploymentCache.SetYamlRedisCache("deployment"+"_"+deploymentName+"_"+namespace+"_Yaml", DeployYaml); err != nil {
			return nil, errors.New("写入缓存数据失败," + err.Error())
		}
		return DeployYaml, nil
	}
	return DeployYaml, nil
}

/*
// 更新deployment
func (d *deployment) UpdateDeployment(namespace, content string) (err error) {
	var deploy = &appsv1.Deployment{}

	err = json.Unmarshal([]byte(content), deploy)
	if err != nil {
		logger.Error(errors.New("反序列化失败, " + err.Error()))
		return errors.New("反序列化失败, " + err.Error())
	}
	_, err = K8s.ClientSet.AppsV1().Deployments(namespace).Update(context.TODO(), deploy, metav1.UpdateOptions{})
	if err != nil {
		logger.Error(errors.New("更新Deployment失败, " + err.Error()))
		return errors.New("更新Deployment失败, " + err.Error())
	}
	return nil
}

*/
